/** 
 * 自动部属G.W.H.脚本到服务器
 * 范用脚本就放着, 用于补位
 */

/**
 * 至于如何占满一台服务器的所有内存. 可以采用穷举法.  (下面的第一脚本:在 G.W.H 三种脚本中, 当前占比最小的那一种)
      a.先部属好所有的目标脚本多线程, 然后剩下的内存去做穷举. 结果存成方案. 结果是当前的第一脚本线程数不变的情况下, 剩下的脚本的组合最优解
      b.第一脚本的线程数减1, 再次穷举, 存方案, 如此往复N次, 直到用尽内存, 或用尽第一脚本的线程数.
      c.上面算出来的方案中, 用最优的方案.
      d.记录 G.W.H三种脚本的部属量, 以在下次部属时计算该部属哪种脚本.
      e.脚本用多少内存动态算出来, 写死的不好维护
      f.上面生成的方案中, 塌缩成多线程, 以减少现实中主机的CPU占用.
 */

import {get_target_server} from "/hack_prepare/get-target-server.js"
import {all_execute_server} from "/hack_prepare/all-execute-server.js"

/**
 * 统一用一个字母指代脚本
 */
const map_script_path = {
  'g': "/hack_prepare/grow.js",
  'w': "/hack_prepare/weaken.js",
  'h':"/hack_prepare/hack.js",
  'e':"/hack_prepare/hack-elementary.js"
};

var GNS = {};// 另一个ns, 作为全局变量方面调用

// 应该的占比 目标占比:
  const target_rate_of_grow = 0.33;
  const target_rate_of_weaken = 0.33;
  const target_rate_of_hack = 0.33;

/**
 * 哪个脚本,目前占比偏小,且偏差最大
 * @return 'g'/'w'/'h'
 */
function witch_too_small(rate_of_grow,rate_of_weaken,rate_of_hack)
{
  var delta1 = rate_of_grow - target_rate_of_grow;
  var delta2 = rate_of_weaken - target_rate_of_weaken;
  var delta3 = rate_of_hack - target_rate_of_hack;

  var smaller_one = 'g';
  if(delta2<delta1)
  {
    smaller_one= 'w';
  }
  if(delta3<delta2)
  {
    smaller_one= 'h';
  }
  else if('g' == smaller_one && delta3<delta1)
  {
    smaller_one= 'h';
  }
  return smaller_one;
}

/**
 * 返回值:
 * {state:1,list_scripts:[],ram_remain:}
 * state:0:成功用到零 1:内存不够 2:内存还有剩 这个字段可以被最后一个字段弃用
 */
async function find_plan_use_remain_ram(ram_remain,script = 'e',list_scripts = [])
{
  if(0==ram_remain)
  {
    // GNS.tprint("0==ram_remain");
    // await GNS.sleep(10);
    return {state:0,list_scripts:list_scripts,ram_remain:0};
  }
  var ram_cost_2 = GNS.getScriptRam(map_script_path[script]);
  // GNS.tprint("75 选择脚本:",script,"\t内存用量:",ram_cost_2);
  var ram_remain_2 = ram_remain-ram_cost_2;
  ram_remain_2 = Math.round(ram_remain_2*100)/100;
  if(0>ram_remain_2)
  {
    // GNS.tprint("0>ram_remain-ram_cost_2");
    // await GNS.sleep(10);
    return {state:1,list_scripts:list_scripts,ram_remain:ram_remain};
  }
  else
  {
    // GNS.tprint("else ram_remain_2");
    // await GNS.sleep(10);
    if(1.6<ram_remain_2)
    {
      var less_ram_remain = 999;
      var best_plan = {};

      for(var i in map_script_path)
      {
        var list2 = list_scripts.slice();
        list2.push(script);
        //GNS.tprint("93 第归调用:","\tram_remain_2:",ram_remain_2,"\ti:",i,"\t[script]:",list2.length,"\t list_scripts:",list_scripts.length);
        var plan_1 = await find_plan_use_remain_ram(ram_remain_2,i,list2);
        if(plan_1.ram_remain<less_ram_remain)
        {
          less_ram_remain = plan_1.ram_remain;
          best_plan = plan_1;
        }
      }// END for
      //GNS.tprint("105 best_plan:",best_plan);
      return best_plan;
    }
    else
    {
      // GNS.tprint("else else");
      // await GNS.sleep(10);
      var list2 = list_scripts;
      list2.push(script);
      return {state:2,list_scripts:list2,ram_remain:ram_remain_2};
    }
  }

}

async function figure_out_plan(ram_total,script_1_cost)
{
  // GNS.tprint("figure_out_plan:",ram_total,",",script_1_cost)
  //await GNS.sleep(10);
  var number_threads_1 = Math.floor(ram_total/script_1_cost);
  var list_plan = [];
  
  // GNS.tprint("119")
  for(var i=0;i<=number_threads_1;++i)
  {
    // GNS.tprint("122")
    var threads_count_1 = number_threads_1-i;
    var ram_remain = ram_total - threads_count_1*script_1_cost;
    var best_plan={};
    {
      var less_ram_remain = 999;
      for(var key in map_script_path)
      {
        //GNS.tprint("127 调用:","find_plan_use_remain_ram","\t第一脚本线程数:",threads_count_1,"\tram_remain:",ram_remain);
        // await GNS.sleep(1000);
        var plan_1 = await find_plan_use_remain_ram(ram_remain,key,[]);
        //GNS.tprint("140 取到的plan_1:",plan_1);
        if(plan_1.ram_remain<less_ram_remain)
        {
          less_ram_remain = plan_1.ram_remain;
          best_plan = plan_1;
        }
      }// END for
    }
    //best_plan
    //await GNS.tprint("第一脚本线程数:", threads_count_1 , "\tram_remain:",ram_remain, "\tbest_plan:",best_plan);

    best_plan.first_script_threads = threads_count_1;
    list_plan.push(best_plan );
    if(0==best_plan.ram_remain)
    {
      // GNS.tprint("已取到最优解:");
      // await GNS.sleep(1000);
      return best_plan;
      break;
    }
  }
  // await GNS.tprint("几个计划:",list_plan.length);

  var best_plan2={};
  var best_remain_ram = 999;
  for(var i=0;i<list_plan.length;++i)
  {
    var plan = list_plan[i];
    if(best_remain_ram>plan.ram_remain)
    {
      best_remain_ram = plan.ram_remain;
      best_plan2 = plan;
    }
  }// END for
  return best_plan2;

  // await GNS.tprint(list_plan);
  return list_plan[0];
  
}

export async function main(ns) {
  GNS = ns;
  // 1. 取目标服务器
  const str_target_server = await get_target_server(ns);

  // 2. 取所有的执行服务器
  var all_executer = await all_execute_server(ns);
  
  // 3. 拷所有的脚本到执行服务器
  const path_script_g = map_script_path['g'];//"/hack_prepare/grow.js";
  const path_script_w = map_script_path['w'];//"/hack_prepare/weaken.js";
  const path_script_h = map_script_path['h'];//"/hack_prepare/hack.js";
  const path_script_e = map_script_path['e'];//"/hack_prepare/hack-elementary.js";
  for(var i=0;i<all_executer.length;++i)
  {
    var executer = all_executer[i];
    ns.scp(path_script_g,executer,"home");
    ns.scp(path_script_w,executer,"home");
    ns.scp(path_script_h,executer,"home");
    ns.scp(path_script_e,executer,"home");
  }

  // 4. 计算部属方案, 根据大局选第一脚本
  var count_of_grow = 0;
  var rate_of_grow = 0;
  var count_of_weaken = 0;
  var rate_of_weaken = 0;
  var count_of_hack = 0;
  var rate_of_hack = 0;
  for(var i=0;i<all_executer.length;++i)
  {
    var executer = all_executer[i];
    // 执行服务器的内存总量:
    var ram_total = ns.getServerMaxRam(executer);
    ns.tprint("执行服务器:",executer ,"\t内存总量:",ram_total ,"\ti=",i);
    //await ns.sleep(100);
    var smaller_one = witch_too_small(rate_of_grow ,rate_of_weaken ,rate_of_hack);
    //ns.tprint("179 smaller_one:",smaller_one);
    var first_script = map_script_path[smaller_one];
    // 第一脚本的内存用量:
    var mem_cost_1 = await ns.getScriptRam(first_script);
    //ns.tprint("183 mem_cost_1:",mem_cost_1);
    // await GNS.sleep(10);
    // ns.tprint("生成计划:");
    // await GNS.sleep(10);
    var plan = await figure_out_plan(ram_total,mem_cost_1);
    ns.tprint("执行服务器:",executer ,"计划:",plan);
    // 5. 跟据部属方案进行部属, 并统计部属
    // 统计计划中的量:
    switch(smaller_one)
    {
      case 'e':
        break;
      case 'g':
        count_of_grow+=plan.first_script_threads;
        break;
      case 'w':
        count_of_weaken+=plan.first_script_threads;
        break;
      case 'h':
        count_of_hack+=plan.first_script_threads;
        break;
    }
    for(var j=0;j<plan.list_scripts.length;++j)
    {
      switch(plan.list_scripts[j])
      {
        case 'e':
          break;
        case 'g':
          count_of_grow+=1;
          break;
        case 'w':
          count_of_weaken+=1;
          break;
        case 'h':
          count_of_hack+=1;
          break;
      }
    }// END for
    // 更新比例:
    var sum_of_scripts = count_of_grow+count_of_weaken+count_of_hack;
    rate_of_grow = count_of_grow/sum_of_scripts;
    rate_of_weaken = count_of_weaken/sum_of_scripts;
    rate_of_hack = count_of_hack/sum_of_scripts;
    // BEGIN 执行:
    await ns.killall(executer);
    if(executer,plan.first_script_threads)
    {
      ns.exec( map_script_path[smaller_one],executer,plan.first_script_threads,str_target_server);
    }
    var map_count = {'g':0,'w':0,'h':0,'e':0};
    for(var j=0;j<plan.list_scripts.length;++j)
    {
      ++map_count[plan.list_scripts[j]];
      // var symbol_script = plan.list_scripts[j];
      // var script_path = map_script_path[symbol_script];
      // ns.exec(script_path,executer,0,str_target_server);
    }
    for(var key in map_count)
    {
      var threads = map_count[key];
      var script_path = map_script_path[key];
      if(threads)
      {
        ns.exec(script_path,executer,threads,str_target_server);
      }
    }// END for
    // END 执行

  }// END 4.

  ns.tprint("目前各脚本的比例:"
    ,"\n\t rate_of_grow",rate_of_grow
    ,"\n\t rate_of_weaken",rate_of_weaken
    ,"\n\t rate_of_hack",rate_of_hack
  );

  // // 6. 取home的剩余内存总量, 留个1TB给其它研究 在home执行
  // var home_max_ram = ns.getServerMaxRam('home');
  // var home_used_ram = ns.getServerUsedRam('home');
  // var home_remain_ram = home_max_ram - home_used_ram;
  // var home_available_ram = home_remain_ram-1000;// 留个1T 给玩家
  // var map_count = {'g':0,'w':0,'h':0,'e':0};
  
  // // 调整执行的脚本数:
  // while(home_available_ram>1.6)
  // {
  //   var smaller_one = witch_too_small(rate_of_grow ,rate_of_weaken ,rate_of_hack);
  //   var path_of_script = map_script_path[smaller_one];
  //   var ram_spent = ns.getScriptRam(path_of_script);
  //   home_available_ram-=ram_spent;
  //   ++map_count[smaller_one];
  //   // 更新数目:
  //   switch(smaller_one)
  //   {
  //   case 'g':++count_of_grow;break;
  //   case 'w':++count_of_weaken;break;
  //   case 'h':++count_of_hack;break;
  //   }
  //   // 更新比例:
  //   var sum_of_scripts = count_of_grow+count_of_weaken+count_of_hack;
  //   rate_of_grow = count_of_grow/sum_of_scripts;
  //   rate_of_weaken = count_of_weaken/sum_of_scripts;
  //   rate_of_hack = count_of_hack/sum_of_scripts;
  // }
  // // 在home执行:
  // for(var key in map_count)
  // {
  //   var threads = map_count[key];
  //   var script_path = map_script_path[key];
  //   if(threads)
  //   {
  //     ns.exec(script_path,'home',threads,str_target_server);
  //   }
  // }// END for
  
  // 7. 在home部属目标服务器的信息监控
  

}